use plotters::coord::ranged1d::AsRangedCoord;
use plotters::coord::ranged1d::DefaultFormatting;
use plotters::coord::ranged1d::KeyPointHint;
use plotters::coord::ranged1d::Ranged;
use plotters::coord::types::RangedCoordf64;
use plotters::prelude::LogScalable;
use std::ops::Range;

pub struct InverseLogCoord<V: LogScalable> {
    linear: RangedCoordf64,
    logic: Range<V>,
    marker: std::marker::PhantomData<V>,
}

impl<V: LogScalable> Ranged for InverseLogCoord<V> {
    type FormatOption = DefaultFormatting;
    type ValueType = V;

    fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
        let value = value.as_f64();
        let value = (1.0 - value).max(1.0 - self.logic.end.as_f64()).ln();
        let v = self.linear.map(&value, limit);
        (limit.0 + limit.1) - v
    }

    fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType> {
        let max_points = hint.max_num_points();
        let tier_1 = (self.logic.end.as_f64() / self.logic.start.as_f64())
            .log10()
            .abs()
            .floor()
            .max(1.0) as usize;

        let tier_2_density = if max_points < tier_1 {
            0
        } else {
            let density = 1 + (max_points - tier_1) / tier_1;
            let mut exp = 1;
            while exp * 10 <= density {
                exp *= 10;
            }
            exp - 1
        };

        let mut multiplier = 10.0;
        let mut cnt = 1;
        while max_points < tier_1 / cnt {
            multiplier *= 10.0;
            cnt += 1;
        }

        let mut ret = vec![];
        let mut inverse_val = (10f64).powf((1.0 - self.logic.end.as_f64()).log10().ceil());

        while inverse_val <= 1.0 - self.logic.start.as_f64() {
            ret.push(V::from_f64(1.0 - inverse_val));
            for i in 1..=tier_2_density {
                let v = inverse_val
                    * (1.0
                        + multiplier / f64::from(tier_2_density as u32 + 1) * f64::from(i as u32));
                if v > 1.0 - self.logic.start.as_f64() {
                    break;
                }
                ret.push(V::from_f64(1.0 - inverse_val));
            }
            inverse_val *= multiplier;
        }

        ret.reverse();
        ret
    }

    fn range(&self) -> Range<Self::ValueType> {
        self.logic.clone()
    }
}

pub struct InverseLogRange<V: LogScalable>(pub Range<V>);

impl<V: LogScalable> AsRangedCoord for InverseLogRange<V> {
    type CoordDescType = InverseLogCoord<V>;
    type Value = V;
}

impl<V: LogScalable> From<InverseLogRange<V>> for InverseLogCoord<V> {
    fn from(range: InverseLogRange<V>) -> InverseLogCoord<V> {
        InverseLogCoord {
            linear: ((1.0 - range.0.end.as_f64()).ln()..(1.0 - range.0.start.as_f64()).ln()).into(),
            logic: range.0,
            marker: std::marker::PhantomData,
        }
    }
}
